跳到主要内容

Code Review for AI Code

06 Agentic Engineering 强调:"AI 写的代码也是你的代码"。但 AI 写的代码和人类写的有结构性差异,传统 review 习惯会漏掉关键问题。本篇讲清楚 AI 代码的特点、必查清单、以及自动化工具的工程边界。

学前说明

很多团队 review AI 代码用的还是 review 同事代码的方式:看看风格、扫一眼逻辑、跑下测试,OK 合并。

这在 AI 代码上风险显著更高,原因有三:

  1. AI 不会因为不确定而停下——它会"自信地写错"
  2. AI 不会因为不熟而问你——它会编造 API、参数、行为
  3. AI 写得太快——你审查不过来就草草 LGTM

2025 年下半年到 2026 年,这造成了大量隐蔽 bug 进入生产。本篇是给资深工程师的"AI 代码 review 手册"。

学习目标

  • 理解 AI 代码与人类代码的 7 个结构性差异
  • 掌握 AI 代码的 12 类常见隐患模式
  • 设计两阶段 review 流程(机械检查 + 人类深度审查)
  • 评估 CodeRabbit / Greptile 等自动化 review 工具的边界
  • 建立团队级 AI 代码 review 规范

与现有知识的衔接

  • 06 Agentic Engineering 第 2.6 节:代码审查文化(前置)
  • 05 Parallel Agents:并行 Agent 让 review 更难
  • 04 Lethal Trifecta:review 安全相关代码的特别注意

第一章:AI 代码的 7 个结构性差异

不是说 AI 代码"质量差",而是它和人类代码性质不同。理解差异是 review 的前提。

1.1 一致性 vs 变化性

人类代码:风格高度一致。同一个工程师写的代码,命名、错误处理、缩进风格基本统一。

AI 代码:在同一个 PR 里可能混用多种风格。前 100 行用 Result<T, E>,后 100 行用 try/catch。原因:AI 没有"长期个人偏好",每次生成时倾向最近上下文里的模式。

Review 影响

  • 不能假设"前面看了几个函数,后面也是这样"
  • 必须全文一致性检查

1.2 命名的"似是而非"

人类:命名经过深思熟虑,反映对业务的理解。

AI:命名"听起来对",但可能不准确。

// AI 写的(看起来很合理)
function processUserData(user: User) {
// 实际上只读不改,但名字是 process
}

function getUser(id: string) {
// 实际上有副作用:缓存了结果
}

function validateEmail(email: string): boolean {
// 实际上只检查 @ 存在与否,远不如名字暗示的严格
}

Review 影响

  • 不能光看函数名判断作用
  • 必须读实现确认名实相符
  • 命名不准的直接改

1.3 边界条件覆盖

人类:经验丰富的工程师会自然想到 null、空数组、负数、超大值。

AI:常常缺失边界处理。它"知道"应该处理,但忘了具体处理。

// AI 经常这样
function getFirstItem<T>(arr: T[]): T {
return arr[0]; // 如果空数组返回 undefined,但类型说是 T
}

// 应该
function getFirstItem<T>(arr: T[]): T | undefined {
return arr[0];
}

Review 影响

  • 重点检查所有"输入域"是否被覆盖
  • 类型签名是否反映了真实返回值

1.4 错误处理的形式主义

人类:会想"这里失败会怎样",针对性处理。

AI:经常加 try/catch 但 catch 里什么都不做,或者只是 console.log。

// AI 经常这样
try {
const data = await fetchUserProfile(id);
return data;
} catch (error) {
console.error(error); // 然后呢?
return null; // 调用方完全不知道发生了什么
}

// 应该
try {
return await fetchUserProfile(id);
} catch (error) {
if (error instanceof NotFoundError) {
throw new UserNotFoundError(id);
}
if (error instanceof NetworkError) {
return await fetchUserProfile(id); // 重试一次
}
logger.error('Unexpected error fetching profile', { id, error });
throw error; // 不认识的错误向上抛
}

Review 影响

  • 每个 try/catch 必须问"catch 里在干什么"
  • "console.log 然后 return null" 是几乎肯定的 bug

1.5 抽象的过度或不足

人类:根据具体场景判断抽象层次。

AI:要么把 5 行代码拆成 3 个函数(过度),要么把 200 行塞一个函数(不足)。

过度抽象的特征:

  • BaseAbstractFactoryProvider 类型的命名
  • 一个函数被一个文件调用
  • 接口只有一个实现

不足抽象的特征:

  • 200+ 行的函数
  • 大量重复的逻辑块
  • 嵌套 4 层以上的 if

Review 影响

  • 主动寻找抽象层次问题
  • 不要被"看起来很专业"迷惑

1.6 测试的"纸面通过"

人类:写测试时通常想"这测试能不能真的发现 bug"。

AI:经常写"能跑通的测试",但实际上只覆盖 happy path,或者测试在测自己。

// AI 经常这样(看起来覆盖了)
it('should validate email', () => {
expect(validateEmail('foo@bar.com')).toBe(true);
});

// 缺失:'foo@bar' (无 .com), 'foo' (无 @), '' (空), null

更糟糕的:

// AI 在 mock 里把答案"写死"
it('should fetch user', async () => {
vi.mocked(api.getUser).mockResolvedValue({ id: '123', name: 'Alice' });
const result = await getUser('123');
expect(result).toEqual({ id: '123', name: 'Alice' });
// 这个测试只是测试"mock 返回什么就是什么",没测真实逻辑
});

Review 影响

  • 测试覆盖率高 ≠ 质量高
  • 看每个测试是否有真正的断言能力
  • mock 是不是过度,导致测试失去意义

1.7 上下文断裂

人类:写代码时"记得"前面写了什么,整个文件有连贯逻辑。

AI:context window 有限,长文件后半部分可能"忘了"前半部分的约定。

典型表现:

  • 同一个文件里,错误处理风格不一致
  • 同一个 class 里,方法签名风格不一致
  • 跨文件不一致更严重

Review 影响

  • 必须全文扫描,不能局部审视
  • 跨文件的约定一致性更要警惕

第二章:AI 代码的 12 类常见隐患

按发生频率和危险等级排序。

2.1 P0:编造的 import / API

症状:AI 引入了不存在的库、不存在的函数、不存在的字段。

import { advancedRetry } from 'axios'; // axios 没有这个导出
import { LRUCache } from 'lodash'; // lodash 没有

const result = supabase
.from('users')
.selectWithCount(); // 不存在的方法

为什么 AI 会这样:训练数据里见过类似的库 API,混淆或编造合理的方法名。

检测

  • TypeScript 编译会报错(必须 tsc --noEmit 干净)
  • 运行时才暴露的:JS 弱类型、动态 import
  • 必须实际跑测试或运行,不能只看 diff

2.2 P0:硬编码的密钥/敏感信息

// AI 看到 .env.example 里的占位符,可能直接复制
const apiKey = "sk-1234567890abcdef"; // 这可能是真的 key
const dbUrl = "postgres://user:realPassword@host/db";

检测

  • pre-commit hook 扫描密钥
  • AI 改完后必看 git diff 中的 .env / config 文件

2.3 P0:无声吞掉的错误

try {
await criticalOperation();
} catch (e) {} // 完全静默

try {
await fetchData();
} catch (e) {
console.log(e); // 生产环境看不到
}

检测

  • grep 项目里所有空 catch block
  • grep console.log in catch

2.4 P1:类型欺骗

const user = data as User; // 强制断言,没验证
const id: string = response.id!; // ! 断言,假设非空
function getValue(): unknown {
return data as any; // 用 any 绕过类型
}

检测

  • grep as anyas unknown as@ts-ignore@ts-expect-error
  • 每个都问"为什么需要绕过类型"

2.5 P1:异步竞态条件

// AI 经常忘记 await
function saveUser(user: User) {
db.save(user); // 没 await,可能没保存就返回了
return { success: true };
}

// 或者忘记并发安全
async function increment() {
const value = await getValue();
await setValue(value + 1);
// 两个并发调用 → 丢更新
}

检测

  • ESLint @typescript-eslint/no-floating-promises
  • 心里过一遍"这里会不会并发问题"

2.6 P1:未处理的边界值

function calculateAverage(numbers: number[]): number {
return numbers.reduce((a, b) => a + b) / numbers.length;
// 空数组:reduce 抛错;1 个元素:忘了初始值
// 空数组:除以 0 = NaN
}

检测

  • 每个函数想三个 case:空、单元素、极端值
  • 边界测试覆盖

2.7 P2:性能反模式

// AI 喜欢嵌套循环
for (const user of users) {
const orders = await getOrders(user.id); // N+1 查询
// ...
}

// AI 不知道你的数据量
const sorted = data.sort(...); // 100万条数据原地排序

检测

  • 看循环里的 await
  • 看大数组操作
  • 必要时上 profiler

2.8 P2:依赖污染

// AI 引入了不必要的大依赖
"dependencies": {
"lodash": "^4.17.21", // 你只用了 isEmpty
"moment": "^2.29.4" // 已废弃,应该用 dayjs
}

检测

  • 每次 package.json 改动重点 review
  • pnpm dlx depcheck 找未用依赖

2.9 P2:测试质量低

详见 1.6 节。

2.10 P3:注释过度或不足

AI 倾向写过度的注释

// 增加用户
function addUser(user: User) {
// 把用户加进来
users.push(user);
}

或者跨文件解释 TypeScript 类型

// 这个是字符串
const name: string;

检测

  • 删掉无意义的注释
  • 保留"为什么"的注释

2.11 P3:风格漂移

详见 1.7 节。

2.12 P3:过时的最佳实践

AI 的训练数据有 cutoff。可能用:

  • React class component(应该用 hooks)
  • componentWillMount(已废弃)
  • Promise.then 链(应该用 async/await)
  • enum(应该用 as const)

检测

  • 项目级 ESLint 规则
  • 团队 CLAUDE.md 明确规定"用什么风格"

第三章:两阶段 Review 流程

3.1 流程总览

核心原则

  1. Stage 1 100% 自动化——人不应该看 lint/typecheck 错误
  2. Stage 2 集中精力在"机械检查不到的地方"
  3. 不要跳过 Stage 1 直接 Stage 2——浪费人脑

3.2 Stage 1:机械检查清单

必须全部自动化跑,且必须 0 错误才进 Stage 2:

# .github/workflows/ai-pr-check.yml
name: AI PR Mechanical Check
on:
pull_request:
types: [opened, synchronize]

jobs:
mechanical-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# 1. 类型安全
- name: Type check
run: pnpm typecheck

# 2. Lint
- name: Lint
run: pnpm lint -- --max-warnings 0

# 3. 测试
- name: Test
run: pnpm test

# 4. 构建
- name: Build
run: pnpm build

# 5. 密钥扫描
- name: Secret scan
uses: trufflesecurity/trufflehog@main

# 6. 依赖审计
- name: Audit
run: pnpm audit --audit-level moderate

# 7. 测试覆盖率
- name: Coverage
run: pnpm test:coverage -- --coverage.threshold=80

# 8. AI 代码标记检查
- name: Check AI markers
run: ./scripts/check-ai-markers.sh
# 检查:commit message 里是否标注 [AI-assisted] / [AI-generated]

3.3 Stage 2:人类深度审查清单

每个 AI PR 必查的 20 项:

命名与意图(1-3)

  • 函数名实际反映了行为?
  • 变量名暗示的含义和实际值一致?
  • 文件/类名表达了它真的在做什么?

边界与异常(4-7)

  • 所有输入的边界 case 被处理(null / 空 / 极端值)?
  • try/catch 里 catch 不是空的、不是只有 console.log?
  • 网络/IO 失败的处理合理?
  • 异步并发场景没有 race condition?

类型与正确性(8-10)

  • 没有 as any@ts-ignore! 断言(除非有充分理由)?
  • 类型签名反映真实返回值(包括 undefined / null)?
  • 泛型用得对,不是为了用而用?

安全(11-13)

  • 没有硬编码密钥或敏感信息?
  • 用户输入做了校验?
  • 没有引入 SQL/XSS/SSRF 注入风险?

性能(14-16)

  • 循环里没有 N+1 查询/调用?
  • 大数据操作合理(不是 100 万条原地排序)?
  • 没有不必要的重复计算?

测试(17-18)

  • 测试有真实断言能力,不是 mock 后断言 mock?
  • 边界 case 有测试?

一致性(19-20)

  • 风格和项目其他地方一致?
  • 没有引入废弃的库或 API?

3.4 Review 时间预算

参考标准(基于 PR 大小):

PR 行数Stage 1Stage 2总时间
< 50自动5 分钟5 min
50-200自动15 分钟15 min
200-500自动30-45 分钟45 min
> 500自动拒收

原则:超过 500 行的 AI PR 必须拆分,否则 review 不过来。这就是为什么 06 Agentic Engineering 强调"先规划再执行"——好的规划自然产出小 PR。

3.5 双人 review 的特殊价值

对 AI 代码,双人 review 比对人代码更重要:

  • 一个人看了的"明显问题",另一个人能看到"不明显的问题"
  • AI 错误模式有特定特征,多人能覆盖更多模式
  • 高敏感模块(auth、payment、user data)必须双人

实施:在 PR template 里区分

## Review 要求
- [ ] 至少一名工程师 review
- [ ] 涉及 auth/payment/user-data 时需要双人 review
- [ ] AI-generated 代码 + 高敏感模块 = 强制双人

第四章:自动化 Review 工具的边界

4.1 主流工具

2026 年成熟的 AI Code Review 工具:

工具厂商特点主要用途
CodeRabbitCodeRabbit IncGitHub PR 自动评论通用 review
GreptileGreptile大 codebase 上下文 review复杂改动
Cursor BugBotCursorIDE 内 inline 提示写代码时即时
GitHub Copilot ReviewGitHub集成 GitHub PR通用
BitoBito多模型支持团队定制

4.2 这些工具能做什么

强项

  • 找出明显的 bug 模式(null 检查、未处理 promise)
  • 发现风格不一致
  • 标记 TODO 和潜在问题
  • 解释复杂代码段
  • 建议命名改进

弱项

  • 业务逻辑正确性:它不知道你的业务规则
  • 架构层面问题:它只看本 PR 的 diff
  • 性能影响:它没有 profiler 数据
  • 安全的深度问题:可能漏掉复杂注入
  • 测试质量判断:它不知道哪些边界你真的需要

4.3 如何评估自动化工具

试用一周,看:

信号 1:误报率
- 高误报(> 30%)→ 工程师会忽略,等于没用
- 低误报(< 10%)→ 工程师会认真看

信号 2:真阳性
- 它发现的问题,你 review 时也会发现 → 价值低
- 它发现的问题,你可能漏掉 → 价值高

信号 3:响应速度
- PR 提交后 5 分钟内有评论 → 集成进 flow
- > 30 分钟 → 你已经 review 过了,没意义

信号 4:上下文质量
- 它看到的只是 diff → 评论可能不准
- 它能看到全 codebase → 评论更准(Greptile 的优势)

4.4 工具 + 人的最佳组合

工具的任务:
- Stage 1 的扩展(更广的机械检查)
- 一些常见模式(null 检查、性能反模式)
- 给人 review 提示

人的任务:
- 业务逻辑正确性
- 架构合理性
- 测试质量
- 命名意图
- 边界覆盖

不要:
- 让工具替代 Stage 2 人类 review
- 让人重复做工具能做的检查

4.5 自建团队级 Review Bot

很多大团队选择自建:

// 简化思路:基于 LLM 的自定义 Review Bot
async function reviewPR(pr: PullRequest) {
// 1. 拉取 diff + 相关上下文
const diff = await github.getPRDiff(pr);
const relatedFiles = await findRelatedFiles(diff);

// 2. 用 LLM + 团队规范做 review
const review = await llm.review({
diff,
context: relatedFiles,
teamRules: await loadCLAUDEmd(),
historicalPatterns: await loadCommonIssues(),
});

// 3. 过滤误报
const filtered = await filterFalsePositives(review);

// 4. 发到 PR
await github.postReviewComments(pr, filtered);
}

为什么自建:

  • 用团队的 CLAUDE.md 做 review 标准
  • 学习历史 issue 的常见错误模式
  • 不发送代码到第三方(合规)

第五章:团队级 AI Code Review 规范

5.1 PR 模板

## 改动说明

## AI 使用声明
- [ ] 本 PR 包含 AI 辅助生成的代码
- 使用工具:☐ Claude Code ☐ Cursor ☐ Copilot ☐ Codex
- AI 辅助范围:☐ 整体生成 ☐ 局部辅助 ☐ 仅自动补全
- 我已经:☐ 完整读过每一行代码 ☐ 理解每个改动的目的

## 自测清单
- [ ] pnpm typecheck 通过
- [ ] pnpm lint 通过
- [ ] pnpm test 通过
- [ ] 手动测试核心流程
- [ ] 检查无密钥/敏感信息提交
- [ ] 边界 case 已考虑

## Review 重点
说明本 PR 中你最希望 reviewer 关注的部分(特别是 AI 生成的代码)

5.2 Commit Message 规范

区分 AI 参与程度:

# 完全人写
feat(cart): add quantity validator

# AI 辅助(你主导,AI 帮忙)
feat(cart): add quantity validator [AI-assisted]

# AI 主导(AI 写大部分,你 review)
feat(cart): add quantity validator [AI-generated]

# Co-author 标签(GitHub 风格)
feat(cart): add quantity validator

Co-authored-by: Claude <noreply@anthropic.com>

5.3 Review 责任划分

作者(Author):
- 我对这段代码完全负责
- 我能解释每一行的含义
- 我做过自测

Reviewer:
- 我对合并到 main 负责
- 发现问题主动 block
- 不理解的地方主动问

不可以:
- "AI 写的我没看,你看吧"
- "看起来对就 LGTM"
- "测试过了就行"

5.4 培训计划

新成员加入团队(或刚开始用 AI)的培训:

第 1 周:禁止用 AI 提交代码

  • 学项目结构、规范
  • 体验"完全自己写"的感觉

第 2-4 周:只用 AI 补全(Copilot 风格)

  • 不能用 Composer / Agent
  • 学会即时审视 AI 建议

第 2 月:可以用 Coding Agent,但

  • 每个 PR 必须双人 review
  • 必须能完整解释每一行
  • 接受 review 严格反馈

第 3 月+:完整工作流

  • 可以并行 / 异步 Agent
  • review 标准和资深一致

第六章:高频反模式

6.1 "看起来 LGTM"

症状:AI 代码看起来很专业、命名规范、有注释,reviewer 扫一眼就 LGTM。

真实:所有的"看起来"都是 AI 训练目标。它就是要看起来对。

对策

  • 强制 review 时间下限(比如 PR 行数 / 30 分钟)
  • 强制 reviewer 写实质性评论(不只是 "LGTM")
  • 抽查制度:随机抽 10% PR 三人复审

6.2 "AI 又改了 review 提的问题"

症状:你 comment 一个问题,AI 修了,但只修了你 comment 的那个点,类似问题在其他地方还在。

对策

  • comment 时明确说"这类问题在文件里其他地方也有,全部修"
  • review 时主动搜索同类问题
  • 在 CLAUDE.md 写"修问题时检查同类"

6.3 "我看不懂这段,但测试通过"

症状:reviewer 不理解某段代码,但跑测试通过就批准。

真实:你不理解 = 你不能维护。今天 LGTM,6 个月后这块出 bug 你完全不知道怎么改。

对策

  • 看不懂 = 让作者重写
  • 看不懂 = 加详细注释
  • 不允许"测试通过 = 合格"

6.4 "AI 时代不需要 review"

症状:团队里有人主张"AI 写的足够好,节省 review 时间"。

对策:摆数据。让看看过去 1 个月 AI PR 引入了多少 bug。基本上数据立刻打脸。


第七章:度量 review 质量

7.1 关键指标

指标目标说明
PR review 平均时间与 PR 大小成比例跳过严重 = 危险信号
Review 评论数(实质性)> 0每个 PR 至少有 1 条实质评论
上线后 bug 归因率下降review 应该挡住大部分 bug
AI 代码 vs 人代码的 bug 率接近AI 代码 bug 显著高 = review 不够

7.2 抽查机制

每月:

  • 随机抽 10% 已合并 AI PR
  • 三人 review 重新审查
  • 发现 review 漏掉的问题 → 团队学习

每季度:

  • 看上线 bug 中有多少是"PR review 该挡住的"
  • 调整 review 标准

7.3 文化指标

现象健康度
Reviewer 经常发现 AI 代码问题✅ 健康
Reviewer "LGTM" 很多⚠️ 警惕
作者经常因为 review 重写✅ 健康
作者抱怨 review 严格✅ 通常健康(除非过度)
大家不愿意 review❌ 不健康

第八章:实战例子

8.1 例:一段"看起来 OK"的 AI 代码

// AI 生成
export async function syncUserData(userId: string): Promise<UserData | null> {
try {
const remote = await fetchRemoteUser(userId);
const local = await db.users.findById(userId);

if (!remote) return local;
if (!local) {
await db.users.create(remote);
return remote;
}

if (remote.updatedAt > local.updatedAt) {
await db.users.update(userId, remote);
return remote;
}

return local;
} catch (error) {
console.error('Sync failed', error);
return null;
}
}

初看 LGTM?再仔细看

  1. 错误处理return null 让调用方完全不知道是网络问题、找不到用户、还是其他。
  2. 竞态:远端 fetch 和本地查同时进行,但之后的 update 是基于"刚才看到的"local。如果两边并发更新,会丢更新。
  3. 时间比较remote.updatedAt > local.updatedAt —— 如果两个系统时钟不同步呢?
  4. 类型欺骗:返回 UserData | null,但 null 含义是"失败",调用方很难处理。
  5. 测试:这段需要测:远端无、本地无、双方都有但远端新、双方都有但本地新、网络失败、并发情况。AI 大概率只测了 happy path。

每一条都是 review 时该提出的。

8.2 例:依赖升级 PR

AI 升级了 30 个 npm 包。Diff 主要在 package.json + lock 文件,看起来"机械"。

陷阱

  • 某个包从 4.x 到 5.x 是 major 版本——AI 可能没注意 breaking change
  • 某个包名改了(react-spring@react-spring/web),AI 可能没全部跟进
  • 测试通过 ≠ 没问题——很多 breaking change 在 runtime 才暴露

Review 必查

  • 看 changelog(不是 release notes,看真实 CHANGELOG.md)
  • 跑 e2e 测试(不只单元测试)
  • 部署 preview,手动测核心流程

8.3 例:AI 修复"flaky test"

PR 描述:"fix flaky test"。

// 之前
expect(result).toBe(expected);

// AI 改成
expect(result).toBeDefined(); // 弱化断言

这是地震级红旗:AI 把测试改弱,不是修问题。

Review 必须

  • 任何"测试改动"PR 单独严格审
  • 弱化断言 = 几乎肯定 reject
  • 实在不能精确断言,要解释为什么

第九章:未来方向

9.1 AI Review AI

让一个 LLM review 另一个 LLM 写的代码?理论可行,2026 年开始流行:

  • 优点:批量、便宜、覆盖广
  • 缺点:同样的盲区、同样的偏好、可能漏共同模式

最佳实践:用作"机械检查的扩展",不替代人类。

9.2 模型间交叉 review

让 Claude 写代码 + GPT review。不同模型有不同偏好,能捕捉到对方漏掉的问题。

实战:

Claude Code 写代码 → push PR

CodeRabbit (基于 GPT) 自动 review

人类看双方意见后判断

9.3 静态分析 + LLM 结合

传统静态分析(Semgrep、Sonar)+ LLM 解读:

  • 静态分析提供精确警告
  • LLM 解释含义、给修复建议
  • 结合两者优点

9.4 PR 拆分 Bot

AI Agent 主动拆分大 PR:

  • 检测 PR > 500 行
  • 自动按文件 / 关注点拆成多个小 PR
  • 改善 review 效率

权威资料

核对日期:2026-06-12